home *** CD-ROM | disk | FTP | other *** search
/ CD BIT 75 / CD BIT 75.iso / Software / mysql-4.0.22-win / data1.cab / Development / examples / tests / mail_to_db.pl < prev    next >
Encoding:
Perl Script  |  2004-10-28  |  15.4 KB  |  599 lines

  1. #!/usr/bin/perl -w
  2. # Copyright Abandoned 1998 TCX DataKonsult AB & Monty Program KB & Detron HB
  3. # This file is public domain and comes with NO WARRANTY of any kind
  4. #
  5. # This program is brought to you by Janne-Petteri Koilo with the 
  6. # administration of Michael Widenius.
  7. #
  8. # Rewritten with a lot of bug fixes by Jani Tolonen and Thimble Smith
  9. # 15.12.2000
  10. #
  11. # This program takes your mails and puts them into your database. It ignores
  12. # messages with the same from, date and message text.
  13. # You can use mail-files that are compressed or gzipped and ends with
  14. # -.gz or -.Z.
  15.  
  16. use DBI;
  17. use Getopt::Long;
  18.  
  19. $| = 1;
  20. $VER = "2.6";
  21.  
  22. $opt_help          = 0;
  23. $opt_version       = 0;
  24. $opt_debug         = 0;
  25. $opt_host          = undef();
  26. $opt_port          = undef();
  27. $opt_socket        = undef();
  28. $opt_db            = "mail";
  29. $opt_table         = "mails";
  30. $opt_user          = undef();
  31. $opt_password      = undef();
  32. $opt_max_mail_size = 65536;
  33. $opt_create        = 0;
  34. $opt_test          = 0;
  35. $opt_no_path       = 0;
  36. $opt_stop_on_error = 0;
  37. $opt_stdin         = 0;
  38.  
  39. my ($dbh, $progname, $mail_no_from_f, $mail_no_txt_f, $mail_too_big,
  40.     $mail_forwarded, $mail_duplicates, $mail_no_subject_f, $mail_inserted);
  41.  
  42. $mail_no_from_f = $mail_no_txt_f = $mail_too_big = $mail_forwarded =
  43. $mail_duplicates = $mail_no_subject_f = $mail_inserted = 0;
  44. $mail_fixed=0;
  45.  
  46. #
  47. # Remove the following message-ends from message
  48. #
  49. @remove_tail= (
  50. "\n-*\nSend a mail to .*\n.*\n.*\$",
  51. "\n-*\nPlease check .*\n.*\n\nTo unsubscribe, .*\n.*\n.*\nIf you have a broken.*\n.*\n.*\$",
  52. "\n-*\nPlease check .*\n(.*\n){1,3}\nTo unsubscribe.*\n.*\n.*\$",
  53. "\n-*\nPlease check .*\n.*\n\nTo unsubscribe.*\n.*\$",
  54. "\n-*\nTo request this thread.*\nTo unsubscribe.*\n.*\.*\n.*\$",
  55. "\n -*\n.*Send a mail to.*\n.*\n.*unsubscribe.*\$",
  56. "\n-*\nTo request this thread.*\n\nTo unsubscribe.*\n.*\$"
  57. );
  58.  
  59. # Generate regexp to remove tails where the unsubscribed is quoted
  60. {
  61.   my (@tmp, $tail);
  62.   @tmp=();
  63.   foreach $tail (@remove_tail)
  64.   {
  65.     $tail =~ s/\n/\n[> ]*/g;
  66.     push(@tmp, $tail);
  67.   }
  68.   push @remove_tail,@tmp;
  69. }
  70.  
  71. my %months = ('Jan' => 1, 'Feb' => 2, 'Mar' => 3, 'Apr' => 4, 'May' => 5,
  72.           'Jun' => 6, 'Jul' => 7, 'Aug' => 8, 'Sep' => 9, 'Oct' => 10,
  73.           'Nov' => 11, 'Dec' => 12);
  74.  
  75. $progname = $0;
  76. $progname =~ s/.*[\/]//;
  77.  
  78. main();
  79.  
  80. ####
  81. #### main sub routine
  82. ####
  83.  
  84. sub main
  85. {
  86.   my ($connect_arg, @args, $ignored, @defops, $i);
  87.  
  88.   if (defined(my_which("my_print_defaults")))
  89.   {
  90.     @defops = `my_print_defaults mail_to_db`;
  91.     chop @defops;
  92.     splice @ARGV, 0, 0, @defops;
  93.   }
  94.   else
  95.   {
  96.     print "WARNING: No command 'my_print_defaults' found; unable to read\n";
  97.     print "the my.cnf file. This command is available from the latest MySQL\n";
  98.     print "distribution.\n";
  99.   }
  100.   GetOptions("help","version","host=s","port=i","socket=s","db=s","table=s",
  101.          "user=s","password=s","max_mail_size=i","create","test",
  102.          "no_path","debug","stop_on_error","stdin")
  103.   || die "Wrong option! See $progname --help\n";
  104.  
  105.   usage($VER) if ($opt_help || $opt_version ||
  106.           (!$ARGV[0] && !$opt_create && !$opt_stdin));
  107.  
  108.   # Check that the given inbox files exist and are regular files
  109.   for ($i = 0; ! $opt_stdin && defined($ARGV[$i]); $i++)
  110.   {
  111.     die "FATAL: Can't find inbox file: $ARGV[$i]\n" if (! -f $ARGV[$i]);
  112.   }
  113.  
  114.   $connect_arg = "DBI:mysql:";
  115.   push @args, "database=$opt_db" if defined($opt_db);
  116.   push @args, "host=$opt_host" if defined($opt_host);
  117.   push @args, "port=$opt_port" if defined($opt_port);
  118.   push @args, "mysql_socket=$opt_socket" if defined($opt_socket);
  119.   push @args, "mysql_read_default_group=mail_to_db";
  120.   $connect_arg .= join ';', @args;
  121.   $dbh = DBI->connect("$connect_arg", $opt_user, $opt_password,
  122.              { PrintError => 0})
  123.   || die "Couldn't connect: $DBI::errstr\n";
  124.  
  125.   die "You must specify the database; use --db=" if (!defined($opt_db));
  126.   die "You must specify the table; use --table=" if (!defined($opt_table));
  127.  
  128.   create_table($dbh) if ($opt_create);
  129.  
  130.   if ($opt_stdin)
  131.   {
  132.     open(FILE, "-");
  133.     process_mail_file($dbh, "READ-FROM-STDIN");
  134.   }
  135.   else
  136.   {
  137.     foreach (@ARGV)
  138.     {
  139.       # Check if the file is compressed
  140.       if (/^(.*)\.(gz|Z)$/)
  141.       {
  142.     open(FILE, "zcat $_ |");
  143.     process_mail_file($dbh, $1);
  144.       }
  145.       else
  146.       {
  147.     open(FILE, $_);
  148.     process_mail_file($dbh, $_);
  149.       }
  150.     }
  151.   }
  152.   $dbh->disconnect if (!$opt_test);
  153.  
  154.   $ignored = ($mail_no_from_f + $mail_no_subject_f + $mail_no_txt_f +
  155.           $mail_too_big + $mail_duplicates + $mail_fixed);
  156.   print "################################ Mail Report #################################\n\n";
  157.   print "Mails inserted:\t\t\t\t\t$mail_inserted\n";
  158.   print "---------------                                ";
  159.   print "=" . "=" x length("$mail_inserted") . "=\n\n";
  160.   if ($ignored)
  161.   {
  162.     print "Ignored mails\n";
  163.     print "-------------\n";
  164.     if ($mail_no_from_f)
  165.     {
  166.       print "Reason: mail without \"From:\" -field:\t\t$mail_no_from_f\n";
  167.     }
  168.     else
  169.     {
  170.       print "";
  171.     }
  172.     if ($mail_no_txt_f)
  173.     {
  174.       print "Reason: mail without message:\t\t\t$mail_no_txt_f\n";
  175.     }
  176.     else
  177.     {
  178.       print "";
  179.     }
  180.     if ($mail_no_subject_f)
  181.     {
  182.       print "Reason: mail without subject:\t\t\t$mail_no_subject_f\n";
  183.     }
  184.     else
  185.     {
  186.       print "";
  187.     }
  188.     if ($mail_too_big)
  189.     {
  190.       print "Reason: mail too big, over $opt_max_mail_size bytes:\t\t";
  191.       print $mail_too_big;
  192.       print " (see --max_mail_size=#)\n";
  193.     }
  194.     else
  195.     {
  196.       print "";
  197.     }
  198.     if ($mail_duplicates)
  199.     {
  200.       print "Reason: duplicate mail, or in db already:\t$mail_duplicates\n";
  201.     }
  202.     else
  203.     {
  204.       print "";
  205.     }
  206.     if ($mail_fixed)
  207.     {
  208.       print "Reason: mail was an unsubscribe - mail:\t\t$mail_fixed\n";
  209.     }
  210.     else
  211.     {
  212.       print "";
  213.     }
  214.     print "                                               ";
  215.     print "=" . "=" x length("$ignored") . "=\n";
  216.     print "Total number of ignored mails:\t\t\t$ignored\n\n";
  217.   }
  218.   print "Total number of mails:\t\t\t\t"; 
  219.   print $mail_inserted + $ignored;
  220.   print " (OK: ";
  221.   print sprintf("%.1f", (($mail_inserted / ($mail_inserted+$ignored)) * 100));
  222.   print "% Ignored: ";
  223.   print sprintf("%.1f", (($ignored / ($mail_inserted + $ignored)) * 100));
  224.   print "%)\n";
  225.   print "################################ End Report ##################################\n";
  226.   exit(0);
  227. }
  228.  
  229. ####
  230. #### table creation
  231. ####
  232.  
  233. sub create_table
  234. {
  235.   my ($dbh) = @_;
  236.   my ($sth, $query);
  237.  
  238.   $query = <<EOF;
  239. CREATE TABLE $opt_table
  240. (
  241.  mail_id MEDIUMINT UNSIGNED NOT NULL auto_increment,
  242.  date DATETIME NOT NULL,
  243.  time_zone VARCHAR(20),
  244.  mail_from VARCHAR(120) NOT NULL,
  245.  reply VARCHAR(120),
  246.  mail_to TEXT,
  247.  cc TEXT,
  248.  sbj VARCHAR(200),
  249.  txt MEDIUMTEXT NOT NULL,
  250.  file VARCHAR(64) NOT NULL,
  251.  hash INTEGER NOT NULL,
  252.  KEY (mail_id),
  253.  PRIMARY KEY (mail_from, date, hash))
  254.  TYPE=MyISAM COMMENT=''
  255. EOF
  256.   $sth = $dbh->prepare($query) or die $DBI::errstr;
  257.   $sth->execute() or die "Couldn't create table: $DBI::errstr\n";
  258. }
  259.  
  260. ####
  261. #### inbox processing. Can be either a real file, or standard input.
  262. ####
  263.  
  264. sub process_mail_file
  265. {
  266.   my ($dbh, $file_name) = @_;
  267.   my (%values, $type, $check);
  268.  
  269.   $file_name =~ s/.*[\/]// if ($opt_no_path);
  270.  
  271.   %values = ();
  272.   $type = "";
  273.   $check = 0;
  274.   while (<FILE>)
  275.   {
  276.     chop;
  277.     chop if (substr($_, -1, 1) eq "\r");
  278.     if ($type ne "message")
  279.     { 
  280.       if (/^Reply-To: (.*)/i)
  281.       {
  282.     $type = "reply";
  283.     $values{$type} = $1;
  284.       }
  285.       elsif (/^From: (.*)/i)
  286.       {
  287.     $type = "from";
  288.     $values{$type} = $1;
  289.       }
  290.       elsif (/^To: (.*)/i)
  291.       {
  292.     $type = "to";
  293.     $values{$type} = $1;
  294.       }
  295.       elsif (/^Cc: (.*)/i)
  296.       {
  297.     $type = "cc";
  298.     $values{$type} = $1;
  299.       }
  300.       elsif (/^Subject: (.*)/i)
  301.       {
  302.     $type = "subject";
  303.     $values{$type} = $1;
  304.       }
  305.       elsif (/^Date: (.*)/i)
  306.       {
  307.     date_parser($1, \%values, $file_name);
  308.     $type = "rubbish";
  309.       }
  310.       elsif (/^[\w\W-]+:\s/)
  311.       {
  312.     $type = "rubbish";  
  313.       }
  314.       elsif ($_ eq "")
  315.       { 
  316.     $type = "message";
  317.     $values{$type} = "";
  318.       }
  319.       else
  320.       {
  321.     s/^\s*/ /;
  322.     $values{$type} .= $_;
  323.       }
  324.     }
  325.     elsif ($check != 0 && $_ ne "") # in case of forwarded messages
  326.     {
  327.       $values{$type} .= "\n" . $_;
  328.       $check--;
  329.     }
  330.     elsif (/^From .* \d\d:\d\d:\d\d\s\d\d\d\d/ ||
  331.            /^From .* \d\d\d\d\s\d\d:\d\d:\d\d/)
  332.     {
  333.       $values{'hash'} = checksum("$values{'message'}");
  334.       update_table($dbh, $file_name, \%values);
  335.       %values = ();
  336.       $type = "";
  337.       $check = 0;
  338.     }
  339.     elsif (/-* forwarded message .*-*/i) # in case of forwarded messages
  340.     {
  341.       $values{$type} .= "\n" . $_;
  342.       $check++;
  343.       $mail_forwarded++;
  344.     }
  345.     else
  346.     {
  347.       $values{$type} .= "\n" . $_;
  348.     }
  349.   }
  350.   if (defined($values{'message'}))
  351.   {
  352.     $values{'hash'} = checksum("$values{'message'}");
  353.     update_table($dbh, $file_name, \%values);
  354.   }
  355. }
  356.  
  357. ####
  358. #### get date and timezone
  359. ####
  360.  
  361. sub date_parser
  362. {
  363.   my ($date_raw, $values, $file_name, $tmp) = @_;
  364.  
  365.   # If you ever need to change this test, be especially careful with
  366.   # the timezone; it may be just a number (-0600), or just a name (EET), or
  367.   # both (-0600 (EET), or -0600 (EET GMT)), or without parenthesis: GMT.
  368.   # You probably should use a 'greedy' regexp in the end
  369.   $date_raw =~ /^\D*(\d{1,2})\s+(\w+)\s+(\d{2,4})\s+(\d+:\d+)(:\d+)?\s*(\S+.*)?/;
  370.  
  371.   if (!defined($1) || !defined($2) || !defined($3) || !defined($4) ||
  372.       !defined($months{$2}))
  373.   {
  374.     if ($opt_debug || $opt_stop_on_error)
  375.     {
  376.       print "FAILED: date_parser: 1: $1 2: $2 3: $3 4: $4 5: $5\n";
  377.       print "months{2}: $months{$2}\n";
  378.       print "date_raw: $date_raw\n";
  379.       print "Inbox filename: $file_name\n";
  380.     }
  381.     exit(1) if ($opt_stop_on_error);
  382.     $values->{'date'} = "";
  383.     $values->{'time_zone'} = "";
  384.     return;
  385.   }
  386.   $tmp = $3 . "-" . $months{$2} . "-" . "$1 $4";
  387.   $tmp.= defined($5) ? $5 : ":00";
  388.   $values->{'date'} = $tmp;
  389.   print "INSERTING DATE: $tmp\n" if ($opt_debug);
  390.   $values->{'time_zone'} = $6;
  391. }
  392.  
  393. ####
  394. #### Insert to table
  395. #### 
  396.  
  397. sub update_table
  398. {
  399.   my($dbh, $file_name, $values) = @_;
  400.   my($q, $tail, $message);
  401.  
  402.   if (!defined($values->{'subject'}) || !defined($values->{'to'}))
  403.   {
  404.     $mail_no_subject_f++;
  405.     return;            # Ignore these
  406.   }
  407.   $message = $values->{'message'};
  408.   $message =~ s/^\s*//; # removes whitespaces from the beginning 
  409.  
  410.  restart:
  411.   $message =~ s/[\s\n>]*$//; # removes whitespaces and '>' from the end
  412.   $values->{'message'} = $message;
  413.   foreach $tail (@remove_tail)
  414.   {
  415.     $message =~ s/$tail//;
  416.   }
  417.   if ($message ne $values->{'message'})
  418.   {
  419.     $message =~ s/\s*$//; # removes whitespaces from the end
  420.     $mail_fixed++;
  421.     goto restart;      # Some mails may have duplicated messages
  422.   }
  423.  
  424.   $q = "INSERT INTO $opt_table (";
  425.   $q.= "mail_id,";
  426.   $q.= "date,";
  427.   $q.= "time_zone,";
  428.   $q.= "mail_from,";
  429.   $q.= "reply,";
  430.   $q.= "mail_to,";
  431.   $q.= "cc,";
  432.   $q.= "sbj,";
  433.   $q.= "txt,";
  434.   $q.= "file,";
  435.   $q.= "hash";
  436.   $q.= ") VALUES (";
  437.   $q.= "NULL,";
  438.   $q.= "'" . $values->{'date'} . "',";
  439.   $q.= (defined($values->{'time_zone'}) ?
  440.     $dbh->quote($values->{'time_zone'}) : "NULL");
  441.   $q.= ",";
  442.   $q.= defined($values->{'from'}) ? $dbh->quote($values->{'from'}) : "NULL";
  443.   $q.= ",";
  444.   $q.= defined($values->{'reply'}) ? $dbh->quote($values->{'reply'}) : "NULL";
  445.   $q.= ",";
  446.   $q.= defined($values->{'to'}) ? $dbh->quote($values->{'to'}) : "NULL";
  447.   $q.= ",";
  448.   $q.= defined($values->{'cc'}) ? $dbh->quote($values->{'cc'}) : "NULL"; 
  449.   $q.= ","; 
  450.   $q.= $dbh->quote($values->{'subject'});
  451.   $q.= ",";
  452.   $q.= $dbh->quote($message);
  453.   $q.= ",";
  454.   $q.= $dbh->quote($file_name);
  455.   $q.= ",";
  456.   $q.= "'" . $values->{'hash'} . "'";
  457.   $q.= ")";
  458.  
  459.   # Don't insert mails bigger than $opt_max_mail_size
  460.   if (length($message) > $opt_max_mail_size)
  461.   {
  462.     $mail_too_big++;
  463.   }
  464.   # Don't insert mails without 'From' field
  465.   elsif (!defined($values->{'from'}) || $values->{'from'} eq "")
  466.   {
  467.     $mail_no_from_f++;
  468.   }
  469.   elsif ($opt_test)
  470.   {
  471.     print "$q\n";
  472.     $mail_inserted++;
  473.   }
  474.   # Don't insert mails without the 'message'
  475.   elsif ($message eq "") 
  476.   {
  477.     $mail_no_txt_f++;
  478.   }
  479.   elsif ($dbh->do($q))
  480.   {
  481.     $mail_inserted++;
  482.   }
  483.   # This should never happen. This means that the above q failed,
  484.   # but it wasn't because of a duplicate mail entry
  485.   elsif (!($DBI::errstr =~ /Duplicate entry /))
  486.   {
  487.     die "FATAL: Got error :$DBI::errstr\nAttempted query was: $q\n";
  488.   }
  489.   else
  490.   {
  491.     $mail_duplicates++;
  492.     print "Duplicate mail: query: $q\n" if ($opt_debug);
  493.   }
  494.   $q = "";
  495. }
  496.  
  497. ####
  498. #### In case you have two identical messages we wanted to identify them
  499. #### and remove additionals;  We do this by calculating a hash number of the
  500. #### message and ignoring messages with the same from, date and hash.
  501. #### This function calculates a simple 32 bit hash value for the message.
  502. ####
  503.  
  504. sub checksum
  505. {
  506.   my ($txt)= @_;
  507.   my ($crc, $i, $count);
  508.   $count = length($txt);
  509.   for ($crc = $i = 0; $i < $count ; $i++)
  510.   {
  511.     $crc = (($crc << 1) + (ord (substr ($txt, $i, 1)))) +
  512.       (($crc & (1 << 30)) ? 1 : 0);
  513.     $crc &= ((1 << 31) -1);
  514.   }
  515.   return $crc;
  516. }
  517.  
  518. ####
  519. #### my_which is used, because we can't assume that every system has the
  520. #### which -command. my_which can take only one argument at a time.
  521. #### Return values: requested system command with the first found path,
  522. #### or undefined, if not found.
  523. ####
  524.  
  525. sub my_which
  526. {
  527.   my ($command) = @_;
  528.   my (@paths, $path);
  529.  
  530.   return $command if (-f $command && -x $command);
  531.   @paths = split(':', $ENV{'PATH'});
  532.   foreach $path (@paths)
  533.   {
  534.     $path = "." if ($path eq "");
  535.     $path .= "/$command";
  536.     return $path if (-f $path && -x $path);
  537.   }
  538.   return undef();
  539. }
  540.  
  541. ####
  542. #### usage and version
  543. ####
  544.  
  545. sub usage
  546. {  
  547.   my ($VER)= @_;
  548.   
  549.   if ($opt_version)
  550.   {
  551.     print "$progname version $VER\n";
  552.   } 
  553.   else
  554.   {
  555.     print <<EOF;
  556. $progname version $VER
  557.  
  558. Description: Insert mails from inbox file(s) into a table. This program 
  559. can read group [mail_to_db] from the my.cnf file. You may want to have db
  560. and table set there at least.
  561.  
  562. Usage: $progname [options] file1 [file2 file3 ...]
  563. or:    $progname [options] --create [file1 file2...]
  564. or:    cat inbox | $progname [options] --stdin
  565.  
  566. The last example can be used to read mails from standard input and can
  567. useful when inserting mails to database via a program 'on-the-fly'.
  568. The filename will be 'READ-FROM-STDIN' in this case.
  569.  
  570. Options:
  571. --help             Show this help and exit.
  572. --version          Show the version number and exit.
  573. --debug            Print some extra information during the run.
  574. --host=...         Hostname to be used.
  575. --port=#           TCP/IP port to be used with connection.
  576. --socket=...       MySQL UNIX socket to be used with connection.
  577. --db=...           Database to be used.
  578. --table=...        Table name for mails.
  579. --user=...         Username for connecting.
  580. --password=...     Password for the user.
  581. --stdin            Read mails from stdin.
  582. --max_mail_size=#  Maximum size of a mail in bytes.
  583.                    Beware of the downside letting this variable be too big;
  584.                    you may easily end up inserting a lot of attached 
  585.                    binary files (like MS Word documents etc), which take
  586.                    space, make the database slower and are not really
  587.                    searchable anyway. (Default $opt_max_mail_size)
  588. --create           Create the mails table. This can be done with the first run.
  589. --test           Dry run. Print the queries and the result as it would be.
  590. --no_path          When inserting the file name, leave out any paths of
  591.                    the name.
  592. --stop_on_error    Stop the run, if an unexpected, but not fatal error occurs
  593.                    during the run. Without this option some fields may get
  594.                    unwanted values. --debug will also report about these.
  595. EOF
  596.   }
  597.   exit(0);
  598. }
  599.